package com.sky.service.impl;

import com.sky.DTO.TurnoverStatisticsDTO;
import com.sky.dto.GoodsSalesDTO;
import com.sky.entity.Orders;
import com.sky.mapper.ReportMapper;
import com.sky.service.ReportService;
import com.sky.service.WorkspaceService;
import com.sky.vo.*;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ReportServiceImpl implements ReportService {
    @Autowired
    private ReportMapper reportMapper;

    /**
     * 获取订单状态为已完成的订单的总金额和下单时间的统计数据
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public TurnoverStatisticsDTO turnoverStatistics(LocalDate begin, LocalDate end) {//统计开始时间和结束时间内，订单状态为已完成的订单的总金额和下单时间的统计数据

        //1.营业额统计
        //下单时间统计:获取开始时间到结束时间范围内的所有日期
        List<LocalDate> dateList = new ArrayList<>();
        dateList.add(begin);//开始时间添加到日期列表中
        //如果开始时间和结束时间不是同一天，则需要将结束时间往后推一天
        while (!begin.equals(end)) {
            begin = begin.plusDays(1);
            dateList.add(begin);
        }


        //订单状态为已完成的订单的总金额统计
        List<BigDecimal> turnoverList = new ArrayList<>();
        //遍历日期列表，获取每个日期的订单状态为已完成的订单的总金额
        for (LocalDate localDate : dateList) {
            LocalDateTime startTime = LocalDateTime.of(localDate, LocalTime.MIN);//获取当天的开始时间 00:00:00
            LocalDateTime endTime = LocalDateTime.of(localDate, LocalTime.MAX);//获取当天的结束时间 23:59:59
            //构造用于条件查询订单状态为已完成的订单的总金额的条件
            Map<String, Object> turnoverMap = new HashMap<>();//订单状态,开始时间,结束时间
            turnoverMap.put("startTime", startTime);
            turnoverMap.put("endTime", endTime);
            turnoverMap.put("status", Orders.COMPLETED);

            BigDecimal turnover = reportMapper.turnoverStatistics(turnoverMap);//获取当天的订单状态为已完成的订单的总金额
            //如果这天没有订单状态为已完成的订单,则turnover为0
            turnover = turnover == null ? BigDecimal.ZERO : turnover;//如果turnover为null,则设置为0
            turnoverList.add(turnover);//添加到订单总金额列表中
        }

        //把下单时间列表和当前总金额列表,转换为字符串,用,分隔返回给前端
        TurnoverStatisticsDTO turnoverStatisticsDTO = TurnoverStatisticsDTO.builder().dateList(StringUtils.join(dateList, ",")).turnoverList(StringUtils.join(turnoverList, ",")).build();


        return turnoverStatisticsDTO;
    }

    /**
     * 查询用户统计数据(获取下单用户的统计数据包括新增用户和用户总数)
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public UserReportVO userStatistics(LocalDate begin, LocalDate end) {
        //1.获取用户统计日期列表
        List<LocalDate> dateList = new ArrayList<>();
        dateList.add(begin);//开始时间添加到日期列表中
        //如果开始时间和结束时间不是同一天，则需要将结束时间往后推一天
        while (!begin.equals(end)) {
            begin = begin.plusDays(1);//开始时间加一天
            dateList.add(begin);//把新的开始时间添加到日期列表中,直到和结束时间相等
        }


        //2.获取新增用户统计数据
        List<Long> newUserList = new ArrayList<>();
        //3.获取当天统计的用户总数
        List<Long> turnoverList = new ArrayList<>();
        //查询每个日期的新增用户数
        dateList.forEach(date -> {
            //获取当天的开始时间和结束时间
            LocalDateTime startTime = LocalDateTime.of(date, LocalTime.MIN);
            LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
            //构造用于条件查询新增用户的条件
            Map<String, Object> turnoverMap = new HashMap<>();//订单状态,开始时间,结束时间
            turnoverMap.put("startTime", startTime);//当天的开始时间
            turnoverMap.put("endTime", endTime);//当天的结束时间
            //查询当天新增用户数
            Long newUser = reportMapper.userStatistics(turnoverMap);
            //如果没有新增用户,则设置为0
            newUser = newUser == null ? 0 : newUser;

            newUserList.add(newUser);//添加到新增用户列表中

            //查询每个统计日期的用户统计总数(小于等于当前)
            turnoverMap.put("startTime", null);//开始时间设置为null,查询小于等于当前日期的用户总数
            Long totalUserCount = reportMapper.userStatistics(turnoverMap);//获取小于等于当前日期的用户总数

            turnoverList.add(totalUserCount);//添加到用户总数列表中
        });


        //4.构造返回给前端的视图对象VO
        UserReportVO userReportVO = UserReportVO.builder().newUserList(StringUtils.join(newUserList, ","))//新增用户列表转换为字符串,用,分隔
                .totalUserList(StringUtils.join(turnoverList, ","))//用户总数列表转换为字符串,用,分隔
                .dateList(StringUtils.join(dateList, ","))//日期列表转换为字符串,用,分隔
                .build();

        return userReportVO;//返回视图对象VO
    }

    /**
     * 获取订单统计的数据,所有日期的订单总数,所有日期的有效订单数,每个日期的订单总数,每个日期的有效订单数,订单统计的日期列表,订单完成率=有效订单数/总订单数*100%
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public OrderReportVO ordersStatistics(LocalDate begin, LocalDate end) {
        //1.根据开始日期和结束日期,获取所有日期的列表
        List<LocalDate> dateList = new ArrayList<>();
        dateList.add(begin);//开始时间添加到日期列表中
        //把开始时间到结束时间之间的日期添加到日期列表中
        while (!begin.equals(end)) {//先判断是否到结束日期,如果没有到结束日期,则日期+1,直到到结束日期
            begin = begin.plusDays(1);//开始时间加一天
            dateList.add(begin);//把新的开始时间添加到日期列表中,直到和结束时间相等
        }

        //2.遍历日期列表,获取每一个日期的订单总数列表和有效订单数列表
        List<Long> totalOrderList = new ArrayList<>();//订单总数列表
        List<Long> effectiveOrderList = new ArrayList<>();//有效订单数列表
        dateList.forEach(
                date -> {
                    //获取当天的开始时间和结束时间
                    LocalDateTime startTime = LocalDateTime.of(date, LocalTime.MIN);
                    LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);
                    //构建查询条件,根据开始时间和结束时间查询当天的订单总数和有效订单数
                    Map<String, Object> orderMap = new HashMap<>();//查询条件Map
                    orderMap.put("startTime", startTime);
                    orderMap.put("endTime", endTime);
                    //查询当天的所有订单总数
                    Long totalOrder = reportMapper.ordersStatistics(orderMap);
                    orderMap.put("status", Orders.COMPLETED);//用来查询订单状态为已完成的订单
                    //查询当天的有效订单数
                    Long effectiveOrder = reportMapper.ordersStatistics(orderMap);

                    //把当日订单总数和有效订单数,添加到列表中
                    totalOrder = totalOrder == null ? 0 : totalOrder;//如果totalOrder为null,则设置为0
                    totalOrderList.add(totalOrder);
                    effectiveOrder = effectiveOrder == null ? 0 : effectiveOrder;//如果effectiveOrder为null,则设置为0
                    effectiveOrderList.add(effectiveOrder);
                }
        );


        //3.获取全部日期的订单总数和有效订单数
        long totalOrderCount = 0L;//全部日期的订单总数
        long effectiveOrderCount = 0L;//全部日期的有效订单数
        for (Long orderCount : totalOrderList) {
            totalOrderCount += orderCount;//累加订单总数
        }
        for (Long effectiveOrder : effectiveOrderList) {
            effectiveOrderCount += effectiveOrder;//累加有效订单数
        }

        //4.计算订单完成率
        double orderCompletionRate = (effectiveOrderCount * 1.0) / (totalOrderCount * 1.0);//有效订单数/总订单数*100%


        //5.构造返回给前端的视图对象VO
        OrderReportVO orderReportVO = OrderReportVO.builder()
                .orderCountList(StringUtils.join(totalOrderList, ","))//订单总数列表转换为字符串,用,分隔
                .validOrderCountList(StringUtils.join(effectiveOrderList, ","))//有效订单数列表转换为字符串,用,分隔
                .dateList(StringUtils.join(dateList, ","))//日期列表转换为字符串,用,分隔
                .totalOrderCount((int) totalOrderCount)//全部日期的订单总数
                .validOrderCount((int) effectiveOrderCount)//全部日期的有效订单数
                .orderCompletionRate(orderCompletionRate)//订单完成率
                .build();


        //6.返回视图对象VO
        return orderReportVO;
    }

    /**
     * 获取时间范围内商品名称列表和各个商品的销售数量列表
     *
     * @param begin
     * @param end
     * @return
     */
    @Override
    public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {


        //1.获取商品名称列表和各个商品的销售数量列表(多表联查订单表和订单明细表)
        //获取商品销量统计的开始时间和结束时间
        LocalDateTime startTime = LocalDateTime.of(begin, LocalTime.MIN);//获取开始时间 开始日期的00:00:00
        LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);//获取结束时间 结束日期的23:59:59
        Map<String, Object> top10Map = new HashMap<>();//查询条件Map
        top10Map.put("startTime", startTime);
        top10Map.put("endTime", endTime);
        top10Map.put("status", Orders.COMPLETED);//订单状态为已完成

        //获取开始时间到结束时间内的所有商品名称列表和各个商品名称对应商品的销售数量列表
        List<GoodsSalesDTO> goodsSalesDTOList = reportMapper.getTop10Sales(top10Map);
        List<String> collect = goodsSalesDTOList.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList());
        String nameListStr = StringUtils.join(collect, ",");//把商品名称列表转换为字符串,用,分隔
        //封装返回给前端的视图对象VO
        SalesTop10ReportVO salesTop10ReportVO = SalesTop10ReportVO.builder()
                .nameList(nameListStr)
                .numberList(StringUtils.join(goodsSalesDTOList.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList()), ","))
                .build();


        //2.返回视图对象VO
        return salesTop10ReportVO;
    }

    @Autowired
    private WorkspaceService workspaceService;

    /**
     * 业务:查询近30天的运营数据,并导入Excel文件,然后把Excel文件通过响应输出流,写回前端,下载到浏览器
     *
     * @param response
     */
    @Override
    public void exportOperationalDataReports(HttpServletResponse response) {
        //获取从30天之前到昨天的开始时间和结束时间
        LocalDate begin = LocalDate.now().minusDays(30);
        LocalDate end = LocalDate.now().minusDays(1);

        //获取近30天的概览运营数据,提供为Excel模版文件,创建新的Excel文件,通过response输出流,写回前端,下载到浏览器
        //1.获取近30天的概览运营数据
        BusinessDataVO businessData = workspaceService.getBusinessData(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));

        //获取模版Excel文件的路径
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("tamplate/运营数据报表模板.xlsx");//获取类路径下的tamplate文件夹下的运营数据报表模板.xlsx文件
        System.out.println("项目类路径:" + this.getClass().getClassLoader().getResource("tamplate/运营数据报表模板.xlsx").getPath());//获取项目路径下的tamplate文件夹下的运营数据报表模板.xlsx文件的绝对路径

        //2.基于Excel模版文件,导出概览运营数据,生成新的Excel文件

        try {
            assert resourceAsStream != null;//断言resourceAsStream不为空
            XSSFWorkbook workbook = new XSSFWorkbook(resourceAsStream);//创建excel工作簿对象
            XSSFSheet sheet0 = workbook.getSheetAt(0);//获取第一个sheet页
            sheet0.getRow(1).getCell(1).setCellValue(begin + "至" + end);//获取第二行的第二个单元格,设置日期范围

            //设置概览营运数据
            sheet0.getRow(3).getCell(2).setCellValue(businessData.getTurnover());//获取第三行的第三个单元格,设置总营业额
            sheet0.getRow(3).getCell(4).setCellValue(businessData.getOrderCompletionRate());//获取第三行的第五个单元格,设置订单完成率
            sheet0.getRow(3).getCell(6).setCellValue(businessData.getNewUsers());//获取第三行的第七个单元格,设置新增用户数

            sheet0.getRow(4).getCell(2).setCellValue(businessData.getValidOrderCount());//获取第四行的第三个单元格,设置有效订单数
            sheet0.getRow(4).getCell(4).setCellValue(businessData.getUnitPrice());//获取第四行的第五个单元格,设置平均客单价

            //2.基于Excel模版文件,导出订单明细数据,生成新的Excel文件
            for (int i = 0; i < 30; i++) {
                //当i=0时,Excel的行为7,当i=1时,Excel的行为8,以此类推
                int rowNum = 7 + i;//获取Excel的行号,从7开始
                //获取近30天的订单明细数据
                LocalDate date = begin.plusDays(i);
                BusinessDataVO workspaceServiceBusinessData = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN), LocalDateTime.of(date, LocalTime.MAX));//获取近30天的订单明细数据
                //设置订单明细数据到excel文件中
                sheet0.getRow(rowNum).getCell(1).setCellValue(date.toString());//获取第rowNum行的第二个单元格,设置日期
                sheet0.getRow(rowNum).getCell(2).setCellValue(workspaceServiceBusinessData.getTurnover());//获取第rowNum行的第三个单元格,设置营业额
                sheet0.getRow(rowNum).getCell(3).setCellValue(workspaceServiceBusinessData.getValidOrderCount());//获取第rowNum行的第四个单元格,设置有效订单数
                sheet0.getRow(rowNum).getCell(4).setCellValue(workspaceServiceBusinessData.getUnitPrice());//获取第rowNum行的第五个单元格,设置平均客单价
                sheet0.getRow(rowNum).getCell(5).setCellValue(workspaceServiceBusinessData.getNewUsers());//获取第rowNum行的第六个单元格,设置新增用户数
                sheet0.getRow(rowNum).getCell(6).setCellValue(workspaceServiceBusinessData.getOrderCompletionRate());//获取第rowNum行的第七个单元格,设置订单完成率


            }

            //3.通过response输出流,写回前端,下载到浏览器(/通过输出流将文件下载到客户端浏览器中)
            ServletOutputStream outputStream = response.getOutputStream();//获取响应输出流
            workbook.write(outputStream);//写回前端,把Excel文件写到输出流outputStream中

            //4.关闭资源流
            resourceAsStream.close();//关闭模版文件流
            workbook.close();//关闭工作簿对象
            outputStream.close();//关闭输出流
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}
